﻿using System;
using System.Buffers;
using System.IO;
using System.Threading;
using System.Threading.Tasks;
using System.Xml;

namespace WholeSlideImageLibrary.Formats.DeepZoomDirectory
{
    public class DeepZoomDirectoryImageIdentifier : IImageIdentifier
    {
        public string Name => "DeepZoomDirectory";

        public int HeaderSize => 256;

        private static ReadOnlySpan<byte> s_XmlHeaderBytes => new byte[]
        {
            (byte)'<', (byte)'?', (byte)'x', (byte)'m', (byte)'l', (byte)' '
        };
        private static ReadOnlySpan<byte> s_ImageHeaderBytes => new byte[]
        {
            (byte)'<', (byte)'I', (byte)'m', (byte)'a', (byte)'g', (byte)'e', (byte)' '
        };

        public async ValueTask<ImageSourceType> IdentifyAsync(FileSourceInfo fileInfo, FileSourceReader reader, ReadOnlyMemory<byte> header, CancellationToken cancellationToken = default)
        {
            if (fileInfo is null)
            {
                throw new ArgumentNullException(nameof(fileInfo));
            }
            if (reader is null)
            {
                throw new ArgumentNullException(nameof(reader));
            }

            if (!QuickCheck(header.Span))
            {
                return ImageSourceType.None;
            }

            Stream? stream = null;
            byte[]? buffer = null;
            try
            {
                if (fileInfo is IPhysicalFileSource physicalFileSource)
                {
                    stream = new FileStream(physicalFileSource.PhysicalFilePath, FileMode.Open, FileAccess.Read, FileShare.Read, 4096, true);
                }
                else
                {
                    int bytesToRead = checked((int)fileInfo.FileSize);
                    buffer = ArrayPool<byte>.Shared.Rent(bytesToRead);
                    int bytesRead = await reader.ReadAsync(0, buffer.AsMemory(0, bytesToRead), cancellationToken).ConfigureAwait(false);
                    if (bytesRead != bytesToRead)
                    {
                        throw new InvalidDataException();
                    }
                    stream = new MemoryStream(buffer, 0, bytesToRead);
                }

                DeepZoomManifest? manifest = await ParseDeepZoomXmlAsync(stream).ConfigureAwait(false);
                return manifest is null || manifest.Format is null ? ImageSourceType.None : ImageSourceType.SingleImage;
            }
            finally
            {
                if (!(buffer is null))
                {
                    ArrayPool<byte>.Shared.Return(buffer);
                }
                stream?.Dispose();
            }
        }

        private static ReadOnlySpan<byte> s_trimBytes => new byte[] { (byte)' ', (byte)'\r', (byte)'\n' };

        private static bool QuickCheck(ReadOnlySpan<byte> header)
        {
            header = header.TrimStart(s_trimBytes);
            if (header.IsEmpty)
            {
                return false;
            }
            ReadOnlySpan<byte> xmlHeader = s_XmlHeaderBytes.Slice(0, Math.Min(s_XmlHeaderBytes.Length, header.Length));
            if (header.StartsWith(xmlHeader))
            {
                return true;
            }
            ReadOnlySpan<byte> imageHeader = s_ImageHeaderBytes.Slice(0, Math.Min(s_ImageHeaderBytes.Length, header.Length));
            if (header.StartsWith(imageHeader))
            {
                return true;
            }
            return false;
        }

        public ValueTask<ImageSourceType> IdentifyAsync(DirectorySourceInfo directoryInfo, CancellationToken cancellationToken = default)
        {
            return new ValueTask<ImageSourceType>(ImageSourceType.None);
        }

        private static async Task<DeepZoomManifest?> ParseDeepZoomXmlAsync(Stream stream)
        {
            DeepZoomManifest manifest = new DeepZoomManifest();
            string tmp;
            using (var reader = XmlReader.Create(stream, new XmlReaderSettings { Async = true }))
            {
                while (await reader.ReadAsync().ConfigureAwait(false))
                {
                    if (reader.NodeType == XmlNodeType.Element)
                    {
                        if (reader.Name == "Image")
                        {
                            manifest.Format = reader.GetAttribute("Format");
                            tmp = reader.GetAttribute("TileSize");
                            if (!int.TryParse(tmp, out int tileSize))
                            {
                                return null;
                            }
                            manifest.TileSize = tileSize;
                            tmp = reader.GetAttribute("Overlap");
                            if (!int.TryParse(tmp, out int overlap))
                            {
                                return null;
                            }
                            manifest.Overlap = overlap;
                        }
                        else if (reader.Name == "Size")
                        {
                            tmp = reader.GetAttribute("Width");
                            if (!int.TryParse(tmp, out int width))
                            {
                                return null;
                            }
                            manifest.Width = width;
                            tmp = reader.GetAttribute("Height");
                            if (!int.TryParse(tmp, out int height))
                            {
                                return null;
                            }
                            manifest.Height = height;
                        }
                    }
                }
            }
            return manifest;
        }

        public ValueTask<WholeSlideImageCollection?> OpenImageCollectionAsync(FileSourceInfo fileInfo, FileSourceReader reader, CancellationToken cancellationToken = default)
        {
            throw new NotSupportedException();
        }

        public ValueTask<WholeSlideImageCollection?> OpenImageCollectionAsync(DirectorySourceInfo directoryInfo, CancellationToken cancellationToken = default)
        {
            throw new NotSupportedException();
        }

        public async ValueTask<WholeSlideImage?> OpenSingleImageAsync(FileSourceInfo fileInfo, FileSourceReader reader, CancellationToken cancellationToken = default)
        {
            if (fileInfo is null)
            {
                throw new ArgumentNullException(nameof(fileInfo));
            }
            if (reader is null)
            {
                throw new ArgumentNullException(nameof(reader));
            }

            Stream? stream = null;
            byte[]? buffer = null;
            try
            {
                if (fileInfo is IPhysicalFileSource physicalFileSource)
                {
                    stream = new FileStream(physicalFileSource.PhysicalFilePath, FileMode.Open, FileAccess.Read, FileShare.Read, 4096, true);
                }
                else
                {
                    int bytesToRead = checked((int)fileInfo.FileSize);
                    buffer = ArrayPool<byte>.Shared.Rent(bytesToRead);
                    int bytesRead = await reader.ReadAsync(0, buffer.AsMemory(0, bytesToRead), cancellationToken).ConfigureAwait(false);
                    if (bytesRead != bytesToRead)
                    {
                        throw new InvalidDataException();
                    }
                    stream = new MemoryStream(buffer, 0, bytesToRead);
                }

                DeepZoomManifest? manifest = await ParseDeepZoomXmlAsync(stream).ConfigureAwait(false);
                if (manifest is null || manifest.Format is null)
                {
                    return null;
                }
                return new DeepZoomDirectoryWholeSlideImage(await fileInfo.GetContainingDirectoryAsync(cancellationToken).ConfigureAwait(false), Path.GetFileNameWithoutExtension(fileInfo.FileName), manifest);
            }
            finally
            {
                if (!(buffer is null))
                {
                    ArrayPool<byte>.Shared.Return(buffer);
                }
                stream?.Dispose();
            }
        }

        public ValueTask<WholeSlideImage?> OpenSingleImageAsync(DirectorySourceInfo directoryInfo, CancellationToken cancellationToken = default)
        {
            throw new NotSupportedException();
        }
    }
}
